﻿using System;
using System.Collections.Generic;

namespace Innovative.ProXr
{
	/// <summary>
	/// Specifies a count of relays to turn on or off
	/// when sending a bank directed on/off command
	/// </summary>
	public enum RelayCount
	{
		Zero = 0,
		One = 1,
		Two = 2,
		Three = 3,
		Four = 4,
		Five = 5,
		Six = 6,
		Seven = 7
	}

	/// <summary>
	/// Specifies the status of the relay
	/// </summary>
	public enum RelayStatus
	{
		/// <summary>
		/// The status is unknown
		/// </summary>
		Unknown,
		/// <summary>
		/// The relay is off
		/// </summary>
		Off,
		/// <summary>
		/// The relay is on
		/// </summary>
		On
	}

	/// <summary>
	/// Specifies the relay and relay count
	/// when sending a bank directed on/off command
	/// </summary>
	public class ProXrRelay
	{
		private int _relayId = 0;
		private RelayCount _relayCount = RelayCount.Zero;
		private RelayStatus _relayStatus = RelayStatus.Unknown;
		private IRelayDeviceConfiguration _deviceConfiguration = null;
		private RelayController _device = null;
		private int _bankId = 0;

		/// <summary>
		/// Creates an instance of the Relay object using default values.
		/// </summary>
		public ProXrRelay()
		{
		}

		/// <summary>
		/// Creates an instance of the Relay object with a specified device.
		/// </summary>
		/// <param name="deviceConfiguration"></param>
		public ProXrRelay(IRelayDeviceConfiguration deviceConfiguration)
		{
			this.DeviceConfiguration = deviceConfiguration;
		}

		/// <summary>
		/// Creates an instance of the Relay object with a specified device.
		/// </summary>
		/// <param name="deviceConfiguration"></param>
		public ProXrRelay(IRelayDeviceConfiguration deviceConfiguration, int bank)
		{
			this.DeviceConfiguration = deviceConfiguration;
		}

		/// <summary>
		/// Gets/sets the IRelayDeviceConfiguration device to manage.
		/// </summary>
		public IRelayDeviceConfiguration DeviceConfiguration
		{
			get
			{
				return _deviceConfiguration;
			}
			set
			{
				_deviceConfiguration = value;
				_device = null;
			}
		}

		/// <summary>
		/// Gets/sets the selected bank.
		/// </summary>
		public int BankId
		{
			get
			{
				return _bankId;
			}
			set
			{
				if (value >= 0 && value <= 255)
				{
					_bankId = value;
				}
				else
				{
					throw new ArgumentOutOfRangeException("Bank", "The Bank must be a value between 0 and 255.");
				}
			}
		}

		/// <summary>
		/// Gets/sets ID of the relay
		/// </summary>
		public int RelayId
		{
			get
			{
				return _relayId;
			}
			set
			{
				if (_relayCount <= 0 || _relayId >= 7)
				{
					_relayId = value;
				}
				else
				{
					throw new ArgumentOutOfRangeException("RelayId", "The Relay ID must be a value between 0 and 7.");
				}
			}
		}

		/// <summary>
		/// Specifies the number of relays to effect after the selected relay.
		/// </summary>
		public RelayCount RelayCount
		{
			get
			{
				return _relayCount;
			}
			set
			{
				_relayCount = value;

				int maxValue = 7 - this.RelayId;

				if (((int)_relayCount < 0) || ((int)_relayCount > maxValue))
				{
					throw new ArgumentOutOfRangeException("RelayCount", string.Format("The Relay Count must be greater than zero and less than or equal {0}.", maxValue));
				}
			}
		}

		/// <summary>
		/// Get the current status or sets the desired status
		/// of the specified relay.
		/// </summary>
		public RelayStatus RelayStatus
		{
			get
			{
				return _relayStatus;
			}
			set
			{
				_relayStatus = value;
			}
		}

		public RelayController Device
		{
			get
			{
				if (_device == null)
				{
					_device = new RelayController(this.DeviceConfiguration);
				}

				return _device;
			}
		}

		/// <summary>
		/// Sets the status
		/// </summary>
		/// <returns>True if successful, False otherwise.</returns>
		public bool SetStatus()
		{
			bool returnValue = false;

			if (this.DeviceConfiguration != null)
			{
				RelayByte[] response = this.Device.SendCommand(this.SetStatusCommandBytes());

				if (response.Length == 1 && response[0] == 85)
				{
					returnValue = true;
				}
			}
			else
			{
				throw new DeviceNullException();
			}

			return returnValue;
		}

		/// <summary>
		///  Sets the status of the specific relay
		/// </summary>
		/// <param name="relayId">The relay to update.</param>
		/// <returns>True if successful, False otherwise.</returns>
		public bool SetStatus(int relayId)
		{
			this.RelayId = relayId;
			return this.SetStatus();
		}

		/// <summary>
		/// Updates a specific relay to the desired status.
		/// </summary>
		/// <param name="relayId">The relay to update.</param>
		/// <param name="relayStatus"></param>
		/// <returns>True if successful, False otherwise.</returns>
		public bool SetStatus(int relayId, RelayStatus relayStatus)
		{
			this.RelayId = relayId;
			this.RelayStatus = relayStatus;
			return this.SetStatus();
		}

		/// <summary>
		/// Updates a specific relay to the desired status in the selected bank.
		/// </summary>
		/// <param name="bankId">The Bank of the relay to update.</param>
		/// <param name="relayId">The relay to update.</param>
		/// <param name="relayStatus">The desired status of the relay.</param>
		/// <returns>True if successful, False otherwise.</returns>
		public bool SetStatus(int bankId, int relayId, RelayStatus relayStatus)
		{
			this.BankId = bankId;
			this.RelayId = relayId;
			this.RelayStatus = relayStatus;
			return this.SetStatus();
		}

		/// <summary>
		/// Gets the current status of the relay
		/// </summary>
		/// <returns>True if successful, False otherwise.</returns>
		public RelayStatus GetStatus()
		{
			RelayStatus returnValue = ProXr.RelayStatus.Unknown;

			if (this.DeviceConfiguration != null)
			{
				RelayByte[] response = this.Device.SendCommand(this.GetStatusCommandBytes());

				if (response.Length == 1)
				{
					if (response[0] == 1)
					{
						this.RelayStatus = RelayStatus.On;
						returnValue = RelayStatus.On;
					}
					else if (response[0] == 0)
					{
						this.RelayStatus = RelayStatus.Off;
						returnValue = RelayStatus.Off;
					}
					else
					{
						this.RelayStatus = RelayStatus.Unknown;
						returnValue = RelayStatus.Unknown;
					}
				}
			}
			else
			{
				throw new DeviceNullException();
			}

			return returnValue;
		}

		public RelayStatus GetStatus(int relayId)
		{
			this.RelayId = relayId;
			return this.GetStatus();
		}

		public RelayStatus GetStatus(int bankId, int relayId)
		{
			this.BankId = bankId;
			this.RelayId = relayId;
			return this.GetStatus();
		}

		internal RelayByte[] SetStatusCommandBytes()
		{
			RelayByte[] returnValue = new RelayByte[0];

			// ***
			// *** Add the output bytes to a list which will 
			// *** be converted to an array.
			// ***
			List<RelayByte> items = new List<RelayByte>();

			// ***
			// *** Add the command mode command
			// ***
			items.Add(254);

			// ***
			// *** Add the byte specifying the relay
			// ***
			if (this.RelayStatus == RelayStatus.On)
			{
				items.Add((byte)(this.RelayId + 108));
			}
			else if (this.RelayStatus == RelayStatus.Off)
			{
				items.Add((byte)(this.RelayId + 100));
			}
			else
			{
				throw new ArgumentException("parameter.RelayStatus", "A valid status must be set.");
			}

			// ***
			// *** Add the bank id
			// ***
			items.Add(this.BankId);

			// ***
			// *** Add the relay count if it is greater than 0
			// ***
			if (this.RelayCount > 0)
			{
				items.Add((byte)this.RelayCount);
			}

			// ***
			// *** Convert the list to an array
			// ***
			returnValue = items.ToArray();

			return returnValue;
		}

		internal RelayByte[] GetStatusCommandBytes()
		{
			RelayByte[] returnValue = new RelayByte[0];

			// ***
			// *** Add the output bytes to a list which will 
			// *** be converted to an array.
			// ***
			List<RelayByte> items = new List<RelayByte>();

			// ***
			// *** Add the command mode command
			// ***
			items.Add(254);

			// ***
			// *** Add the byte specifying the relay
			// ***
			items.Add((byte)(this.RelayId + 116));

			// ***
			// *** Add the bank id
			// ***
			items.Add(this.BankId);

			// ***
			// *** Convert the list to an array
			// ***
			returnValue = items.ToArray();

			return returnValue;
		}
	}
}